suppressPackageStartupMessages(library(hdf5r))
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
suppressPackageStartupMessages(library(Seurat))
suppressPackageStartupMessages(library(plotly))
suppressPackageStartupMessages(library(DropletUtils))
suppressPackageStartupMessages(library(scDblFinder))
suppressPackageStartupMessages(library(tidyverse))

Define the arrays to load into memory

The input parameters should specify the location of the H5 raw (unfiltered) cellranger output for preprocessing and the name of the associated dataset, which will be carried forward into downstream analyses. This step also adds an identifier to the label for each cell to ensure they maintain the identity of their respective sample, regardless of file format. Uses a dash and underscore for string splitting downstream to produce metadata for each in terms of the condition and batch.

Arrays were produced using CellRanger 4.0.0 with default settings, using the mm10 2020-A reference (modified vM23/Ens98 annotation).

path <- params$input_path
id <- params$dataset

array <- Read10X_h5(filename = path)
colnames(array) <- paste0(id, "_", colnames(array))

Do basic first-level cell calls using emptyDrops

Uses the approach from Lun et al., 2019 to filter out clearly empty droplets using a dirichlet-multinomial model. Filtration is performed with an FDR<0.001, which results in liberal cell calling to be further filtered downstream. To avoid occassional cases where obvious cells get called as background, all barcodes with >1000 UMIs are assumed non-empty.

cat(paste("Called barcodes:", length(colnames(array))), 
    paste("Mean UMIs/called barcode:", sum(array)/length(colnames(array))),
    paste("Mean Features/called barcode:", sum(array != 0)/length(colnames(array))),
    paste("Percent reads in called barcodes:", plot_array$UMIs_in_cells), 
    paste("Knee point:", plot_array$Knee_threshold),
    paste("Inflection point:", plot_array$Inflection_threshold), sep = "\n")
Called barcodes: 73931
Mean UMIs/called barcode: 1712.39009346553
Mean Features/called barcode: 736.628532009576
Percent reads in called barcodes: 0.969417848398536
Knee point: 9213
Inflection point: 1158

Format into Seurat object, add metadata for regions and quality metrics*

Seurat objects are by far the most versatile and easiest to perform pre-processing on, so all subsequent steps will work with Seurat-formatted data matrices. This step also uses aforementioned string splitting to extract the condition, and it also determines mitochondrial and ribosomal read proportion.

count_stats("counts/cell", array@meta.data$nCount_RNA)
Min counts/cell 101
Max counts/cell 68912
Mean counts/cell 1712.39009346553
Median counts/cell 386
Std. dev. counts/cell 4136.31268011911
Std. error counts/cell 15.2124817796177
count_stats("features/cell", array@meta.data$nFeature_RNA)
Min features/cell 98
Max features/cell 8382
Mean features/cell 736.628532009576
Median features/cell 330
Std. dev. features/cell 1091.95480543401
Std. error features/cell 4.01597844903559
count_stats("mitochondrial %/cell", array@meta.data$Mito_proportion)
Min mitochondrial %/cell 0
Max mitochondrial %/cell 0.0573476702508961
Mean mitochondrial %/cell 0.00267811482820801
Median mitochondrial %/cell 0.00240384615384615
Std. dev. mitochondrial %/cell 0.00299081575586516
Std. error mitochondrial %/cell 1.09995867602016e-05
count_stats("ribosomal %/cell", array@meta.data$Ribo_proportion)
Min ribosomal %/cell 0
Max ribosomal %/cell 0.0396039603960396
Mean ribosomal %/cell 0.00517779632138434
Median ribosomal %/cell 0.00453514739229025
Std. dev. ribosomal %/cell 0.00404804826171147
Std. error ribosomal %/cell 1.48878639471051e-05

Perform complexity filtering

Remove cells with fewer than 1000 features. Some differentially filter cells and glia, but doing both at 1000 should be far less complex, and sufficient for our purposes.

require(Seurat)

# Store pre-filter population metrics for plotting
plot_array <- cbind.data.frame(array$nCount_RNA, array$nFeature_RNA, array$nFeature_RNA >= 1000)
colnames(plot_array) <- c("UMIs", "Genes", "Filtered")

# Remove all cells with fewer than 1000 UMIs
array <- subset(array, cells = colnames(array)[array$nFeature_RNA >= 1000])

# Output new QC metrics
paste("Cells passing 1000 gene complexity filter:", length(colnames(array)))
[1] "Cells passing 1000 gene complexity filter: 6151"
paste("UMIs/cell passing 1000 gene complexity filter:", mean(array$nCount_RNA))
[1] "UMIs/cell passing 1000 gene complexity filter: 7889.12225654365"
paste("Features/cell passing 1000 gene complexity filter:", mean(array$nFeature_RNA))
[1] "Features/cell passing 1000 gene complexity filter: 2808.8858722159"
paste("% mitochondrial/cell passing 1000 gene complexity filter:", mean(array$Mito_proportion))
[1] "% mitochondrial/cell passing 1000 gene complexity filter: 0.000282866245190432"
paste("% ribosomal/cell passing 1000 gene complexity filter:", mean(array$Ribo_proportion))
[1] "% ribosomal/cell passing 1000 gene complexity filter: 0.00183036037082939"
plot_ly(data = plot_array, x = ~UMIs, y = ~Genes, color = ~Filtered) %>%
  add_markers %>%
  layout(title = paste("1000 Gene Complexity Filter:", id))
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels

Perform quality filtering: mitochondrial reads

According to pipeComp (Germain et al., 2020), removing mitochondrial outliers mildly increases pipeline performance if done downstream with SCTransform. This cell filters out high outlier nuclei where their mitochondrial read proportion exceeds Q3+5xIQR.

require(Seurat)

# Store pre-filter population metrics for plotting
plot_array <- cbind.data.frame(array$nCount_RNA, array$Mito_proportion, 
                               array$Mito_proportion >= quantile(array$Mito_proportion)[4]+5*IQR(array$Mito_proportion))
colnames(plot_array) <- c("UMIs", "Mitochondrial_proportion", "Filtered")

# Remove all extreme high outliers for mitochondrial reads, defined as Q3+5*IQR, which is an EXTREMELY lenient standard 
array <- subset(array, cells = colnames(array)[array$Mito_proportion <=
                                                 quantile(array$Mito_proportion)[4]+5*IQR(array$Mito_proportion)])

paste("Cells passing mitochondrial outlier filter:", length(colnames(array)))
[1] "Cells passing mitochondrial outlier filter: 6126"
paste("UMIs/cell passing mitochondrial outlier filter:", mean(array$nCount_RNA))
[1] "UMIs/cell passing mitochondrial outlier filter: 7911.1629121776"
paste("Features/cell passing mitochondrial outlier filter:", mean(array$nFeature_RNA))
[1] "Features/cell passing mitochondrial outlier filter: 2814.70682337578"
paste("% mitochondrial/cell passing mitochondrial outlier filter:", mean(array$Mito_proportion))
[1] "% mitochondrial/cell passing mitochondrial outlier filter: 0.000266849517788897"
paste("% ribosomal/cell passing mitochondrial outlier filter:", mean(array$Ribo_proportion))
[1] "% ribosomal/cell passing mitochondrial outlier filter: 0.0018288011798363"
plot_ly(data = plot_array, x = ~UMIs, y = ~Mitochondrial_proportion, color = ~Filtered) %>%
  add_markers %>%
  layout(title = paste("Mitochondrial Outlier Filter:", id))
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels

Perform quality filtering: ribosomal reads

According to pipeComp (Germain et al., 2020), removing ribosomal outliers mildly increases pipeline performance if done downstream with SCTransform and in tandem with removing mitochondrial read outliers. This cell filters out high outlier nuclei where their ribosomal read proportion exceeds Q3+5xIQR.

require(Seurat)

# Store pre-filter population metrics for plotting
plot_array <- cbind.data.frame(array$nCount_RNA, array$Ribo_proportion, 
                               array$Ribo_proportion >= quantile(array$Mito_proportion)[4]+5*IQR(array$Ribo_proportion))
colnames(plot_array) <- c("UMIs", "Ribosomal_proportion", "Filtered")

# Remove all extreme high outliers for mitochondrial reads, defined as Q3+5*IQR, which is an EXTREMELY lenient standard 
array <- subset(array, cells = colnames(array)[array$Ribo_proportion <=
                                                 quantile(array$Ribo_proportion)[4]+5*IQR(array$Ribo_proportion)])

paste("Cells passing ribosomal outlier filter:", length(colnames(array)))
[1] "Cells passing ribosomal outlier filter: 6121"
paste("UMIs/cell passing ribosomal outlier filter:", mean(array$nCount_RNA))
[1] "UMIs/cell passing ribosomal outlier filter: 7911.61770952459"
paste("Features/cell passing ribosomal outlier filter:", mean(array$nFeature_RNA))
[1] "Features/cell passing ribosomal outlier filter: 2814.67946413985"
paste("% mitochondrial/cell passing ribosomal outlier filter:", mean(array$Mito_proportion))
[1] "% mitochondrial/cell passing ribosomal outlier filter: 0.000266931758172702"
paste("% ribosomal/cell passing ribosomal outlier filter:", mean(array$Ribo_proportion))
[1] "% ribosomal/cell passing ribosomal outlier filter: 0.00182286788770106"
plot_ly(data = plot_array, x = ~UMIs, y = ~Ribosomal_proportion, color = ~Filtered) %>%
  add_markers %>%
  layout(title = paste("Ribosomal Outlier Filter:", id))
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels

TEMPORARY UNTIL SOLO WORKS: remove doublets

require(Seurat)
require(scDblFinder)
require(tidyverse)

sce <- as.SingleCellExperiment(array) %>%
    scDblFinder
Clustering cells...
Identifying top genes per cluster...
Creating ~6121 artifical doublets...
Too many clusters - will create triplets only for the 10 largest clusters.Finding KNN...
Evaluating cell neighborhoods...
Finding threshold...

plot_array <- cbind.data.frame(array$nCount_RNA, 
                               array$nFeature_RNA,
                               sce$scDblFinder.class)
colnames(plot_array) <- c("UMIs", "Features", "Doublet_status")

array <- subset(array, cells = colnames(array)[sce$scDblFinder.class == "singlet"])

paste("Cells passing doublet filter:", length(colnames(array)))
[1] "Cells passing doublet filter: 5769"
paste("UMIs/cell passing doublet filter:", mean(array$nCount_RNA))
[1] "UMIs/cell passing doublet filter: 7585.19188767551"
paste("Features/cell passing doublet filter:", mean(array$nFeature_RNA))
[1] "Features/cell passing doublet filter: 2743.21840873635"
paste("% mitochondrial/cell passing doublet filter:", mean(array$Mito_proportion))
[1] "% mitochondrial/cell passing doublet filter: 0.000270180608499699"
paste("% ribosomal/cell passing doublet filter:", mean(array$Ribo_proportion))
[1] "% ribosomal/cell passing doublet filter: 0.00183325269026899"
plot_ly(data = plot_array, x = ~UMIs, y = ~Features, color = ~Doublet_status) %>%
  add_markers %>%
  layout(title = paste("Doublet Filter:", id))
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels

Save pre-processed matrix for use downstream

saveRDS(array, file = paste0("../cellranger_h5_allraw/", id, "_preprocessed.rds"))
LS0tCnRpdGxlOiAiUHJlcHJvY2Vzc2luZyBOb3RlYm9vayIKYXV0aG9yOiAiSmFtZXMgSG93ZSIKb3V0cHV0OiBodG1sX25vdGVib29rCnBhcmFtczoKICBkYXRlOiAiMjAyMC0wOC0zMSIKICBkYXRhc2V0OiAicGxDb0FhLTEtUjEiCiAgaW5wdXRfcGF0aDogIi4uL2RhdGFzZXRzL2NlbGxyYW5nZXJfaDVfcmF3L3BsQ29BYV8xX1IxX1JOQS5oNSIKICBvdXRwdXRfcGF0aDogIi4uL2RhdGFzZXRzL3ByZXByb2Nlc3NlZF9kYXRhc2V0cyIKLS0tCgpgYGB7ciBzZXR1cH0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoaGRmNXIpKQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShTZXVyYXQpKQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShwbG90bHkpKQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShEcm9wbGV0VXRpbHMpKQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShzY0RibEZpbmRlcikpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHRpZHl2ZXJzZSkpCgpjb3VudF9zdGF0cyA8LSBmdW5jdGlvbih4LCB5KXsKICBjYXQocGFzdGUoIk1pbiIsIHgsIG1pbih5KSksCiAgICAgIHBhc3RlKCJNYXgiLCB4LCBtYXgoeSkpLAogICAgICBwYXN0ZSgiTWVhbiIsIHgsIG1lYW4oeSkpLAogICAgICBwYXN0ZSgiTWVkaWFuIiwgeCwgbWVkaWFuKHkpKSwKICAgICAgcGFzdGUoIlN0ZC4gZGV2LiIsIHgsIHNkKHkpKSwKICAgICAgcGFzdGUoIlN0ZC4gZXJyb3IiLCB4LCBzZCh5KS9zcXJ0KGxlbmd0aCh5KSkpLCAKICAgICAgICAgICAgc2VwID0gIlxuIikKfQpgYGAKCiMjIERlZmluZSB0aGUgYXJyYXlzIHRvIGxvYWQgaW50byBtZW1vcnkKClRoZSBpbnB1dCBwYXJhbWV0ZXJzIHNob3VsZCBzcGVjaWZ5IHRoZSBsb2NhdGlvbiBvZiB0aGUgSDUgcmF3ICh1bmZpbHRlcmVkKSBjZWxscmFuZ2VyIG91dHB1dCBmb3IgcHJlcHJvY2Vzc2luZyBhbmQgdGhlIG5hbWUgb2YgdGhlIGFzc29jaWF0ZWQgZGF0YXNldCwgd2hpY2ggd2lsbCBiZSBjYXJyaWVkIGZvcndhcmQgaW50byBkb3duc3RyZWFtIGFuYWx5c2VzLiBUaGlzIHN0ZXAgYWxzbyBhZGRzIGFuIGlkZW50aWZpZXIgdG8gdGhlIGxhYmVsIGZvciBlYWNoIGNlbGwgdG8gZW5zdXJlIHRoZXkgbWFpbnRhaW4gdGhlIGlkZW50aXR5IG9mIHRoZWlyIHJlc3BlY3RpdmUgc2FtcGxlLCByZWdhcmRsZXNzIG9mIGZpbGUgZm9ybWF0LiBVc2VzIGEgZGFzaCBhbmQgdW5kZXJzY29yZSBmb3Igc3RyaW5nIHNwbGl0dGluZyBkb3duc3RyZWFtIHRvIHByb2R1Y2UgbWV0YWRhdGEgZm9yIGVhY2ggaW4gdGVybXMgb2YgdGhlIGNvbmRpdGlvbiBhbmQgYmF0Y2guCgpBcnJheXMgd2VyZSBwcm9kdWNlZCB1c2luZyBDZWxsUmFuZ2VyIDQuMC4wIHdpdGggZGVmYXVsdCBzZXR0aW5ncywgdXNpbmcgdGhlIG1tMTAgMjAyMC1BIHJlZmVyZW5jZSAobW9kaWZpZWQgdk0yMy9FbnM5OCBhbm5vdGF0aW9uKS4gCgpgYGB7ciAxLXJlYWRfaDUsIHdhcm5pbmcgPSBGQUxTRX0KcGF0aCA8LSBwYXJhbXMkaW5wdXRfcGF0aAppZCA8LSBwYXJhbXMkZGF0YXNldAoKYXJyYXkgPC0gUmVhZDEwWF9oNShmaWxlbmFtZSA9IHBhdGgpCmNvbG5hbWVzKGFycmF5KSA8LSBwYXN0ZTAoaWQsICJfIiwgY29sbmFtZXMoYXJyYXkpKQpgYGAKCiMjIERvIGJhc2ljIGZpcnN0LWxldmVsIGNlbGwgY2FsbHMgdXNpbmcgZW1wdHlEcm9wcwoKVXNlcyB0aGUgYXBwcm9hY2ggZnJvbSBbTHVuIGV0IGFsLiwgMjAxOV0oaHR0cHM6Ly9nZW5vbWViaW9sb2d5LmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvczEzMDU5LTAxOS0xNjYyLXkpIHRvIGZpbHRlciBvdXQgY2xlYXJseSBlbXB0eSBkcm9wbGV0cyB1c2luZyBhIGRpcmljaGxldC1tdWx0aW5vbWlhbCBtb2RlbC4gRmlsdHJhdGlvbiBpcyBwZXJmb3JtZWQgd2l0aCBhbiBGRFI8MC4wMDEsIHdoaWNoIHJlc3VsdHMgaW4gbGliZXJhbCBjZWxsIGNhbGxpbmcgdG8gYmUgZnVydGhlciBmaWx0ZXJlZCBkb3duc3RyZWFtLiBUbyBhdm9pZCBvY2Nhc3Npb25hbCBjYXNlcyB3aGVyZSBvYnZpb3VzIGNlbGxzIGdldCBjYWxsZWQgYXMgYmFja2dyb3VuZCwgYWxsIGJhcmNvZGVzIHdpdGggPjEwMDAgVU1JcyBhcmUgYXNzdW1lZCBub24tZW1wdHkuIAoKYGBge3IgMi1jZWxsX2JhcmNvZGVfY2FsbH0KIyBydW4gZW1wdHlEcm9wcwpiYXJjb2RlX2ZpbHRlciA8LSBlbXB0eURyb3BzKGFycmF5LCByZXRhaW4gPSAxMDAwKQpiYXJjb2RlX2ZpbHRlciRGRFJbaXMubmEoYmFyY29kZV9maWx0ZXIkRkRSKV0gPC0gMQprbmVlX3JhbmtzIDwtIGJhcmNvZGVSYW5rcyhhcnJheSkKCiMgZXhwb3J0IG1ldHJpY3MKcGxvdF9hcnJheSA8LSBsaXN0KGNiaW5kLmRhdGEuZnJhbWUoa25lZV9yYW5rcyRyYW5rLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga25lZV9yYW5rcyR0b3RhbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhcmNvZGVfZmlsdGVyJEZEUiksCiAgICAgICAgICAgICAgICAgICAgIHN1bShiYXJjb2RlX2ZpbHRlciRGRFIgPCAwLjAwMSksCiAgICAgICAgICAgICAgICAgICAgIChzdW0oYXJyYXlbLGJhcmNvZGVfZmlsdGVyJEZEUiA8IDAuMDAxXSkgLyBzdW0oYXJyYXkpKSwKICAgICAgICAgICAgICAgICAgICAga25lZV9yYW5rc0BtZXRhZGF0YSRrbmVlLAogICAgICAgICAgICAgICAgICAgICBrbmVlX3JhbmtzQG1ldGFkYXRhJGluZmxlY3Rpb24pCm5hbWVzKHBsb3RfYXJyYXkpIDwtIGMoIk1ldHJpY3MiLCAiVG90YWwiLCAiVU1Jc19pbl9jZWxscyIsICJLbmVlX3RocmVzaG9sZCIsICJJbmZsZWN0aW9uX3RocmVzaG9sZCIpCmNvbG5hbWVzKHBsb3RfYXJyYXkkTWV0cmljcykgPC0gYygiUmFuayIsICJVTUlzIiwgIkZEUiIpCnJvd25hbWVzKHBsb3RfYXJyYXkkTWV0cmljcykgPC0gYyhrbmVlX3JhbmtzQHJvd25hbWVzKQoKIyBhY3R1YWxseSBmaWx0ZXIgdGhlIGFycmF5CmFycmF5IDwtIGFycmF5WywgcGxvdF9hcnJheSRNZXRyaWNzJEZEUiA8IDAuMDAxXQoKIyBnaXZlIGZpcnN0IHN0YWdlIFFDIG1ldHJpY3MKY2F0KHBhc3RlKCJDYWxsZWQgYmFyY29kZXM6IiwgbGVuZ3RoKGNvbG5hbWVzKGFycmF5KSkpLCAKICAgIHBhc3RlKCJNZWFuIFVNSXMvY2FsbGVkIGJhcmNvZGU6Iiwgc3VtKGFycmF5KS9sZW5ndGgoY29sbmFtZXMoYXJyYXkpKSksCiAgICBwYXN0ZSgiTWVhbiBGZWF0dXJlcy9jYWxsZWQgYmFyY29kZToiLCBzdW0oYXJyYXkgIT0gMCkvbGVuZ3RoKGNvbG5hbWVzKGFycmF5KSkpLAogICAgcGFzdGUoIlBlcmNlbnQgcmVhZHMgaW4gY2FsbGVkIGJhcmNvZGVzOiIsIHBsb3RfYXJyYXkkVU1Jc19pbl9jZWxscyksIAogICAgcGFzdGUoIktuZWUgcG9pbnQ6IiwgcGxvdF9hcnJheSRLbmVlX3RocmVzaG9sZCksCiAgICBwYXN0ZSgiSW5mbGVjdGlvbiBwb2ludDoiLCBwbG90X2FycmF5JEluZmxlY3Rpb25fdGhyZXNob2xkKSwgc2VwID0gIlxuIikKCiMgcmVtb3ZlIGR1cGxpY2F0ZXMsIHdpbGwgY3Jhc2ggbm90ZWJvb2sgYW5kIGNvbXB1dGVyIGlmIHJldGFpbmVkIGluIHBsb3QKcGxvdF9hcnJheSRNZXRyaWNzIDwtIHBsb3RfYXJyYXkkTWV0cmljc1shZHVwbGljYXRlZChwbG90X2FycmF5JE1ldHJpY3MkUmFuayksXQpwbG90X2FycmF5JE1ldHJpY3MgPC0gcGxvdF9hcnJheSRNZXRyaWNzW29yZGVyKHBsb3RfYXJyYXkkTWV0cmljcyRSYW5rKSxdCgojIHByb2R1Y2Uga25lZSBwbG90IGZvciBvdXRwdXQgb2YgZW1wdHlEcm9wcwpwbG90X2x5KGRhdGEgPSBwbG90X2FycmF5JE1ldHJpY3MsIHggPSB+UmFuaywgeSA9IH5VTUlzLCBjb2xvciA9IH5GRFIpICU+JQogIGFkZF9tYXJrZXJzICU+JQogIGxheW91dCh0aXRsZSA9IHBhc3RlKCJVTUkgRWxib3cgUGxvdDoiLCBpZCksIAogICAgICAgICB4YXhpcyA9IGxpc3QodHlwZSA9ICJsb2ciKSwgeWF4aXMgPSBsaXN0KHR5cGUgPSAibG9nIikpCmBgYAoKIyMgRm9ybWF0IGludG8gU2V1cmF0IG9iamVjdCwgYWRkIG1ldGFkYXRhIGZvciByZWdpb25zIGFuZCBxdWFsaXR5IG1ldHJpY3MqCgpTZXVyYXQgb2JqZWN0cyBhcmUgYnkgZmFyIHRoZSBtb3N0IHZlcnNhdGlsZSBhbmQgZWFzaWVzdCB0byBwZXJmb3JtIHByZS1wcm9jZXNzaW5nIG9uLCBzbyBhbGwgc3Vic2VxdWVudCBzdGVwcyB3aWxsIHdvcmsgd2l0aCBTZXVyYXQtZm9ybWF0dGVkIGRhdGEgbWF0cmljZXMuIFRoaXMgc3RlcCBhbHNvIHVzZXMgYWZvcmVtZW50aW9uZWQgc3RyaW5nIHNwbGl0dGluZyB0byBleHRyYWN0IHRoZSBjb25kaXRpb24sIGFuZCBpdCBhbHNvIGRldGVybWluZXMgbWl0b2Nob25kcmlhbCBhbmQgcmlib3NvbWFsIHJlYWQgcHJvcG9ydGlvbi4KCmBgYHtyfQphcnJheSA8LSBDcmVhdGVTZXVyYXRPYmplY3QoYXJyYXkpCiAgCiMgYWRkIG5vbi1udWNsZWFyIHJlYWQgcHJvcG9ydGlvbiBtZXRhZGF0YQpNaXRvX3Byb3BvcnRpb24gPC0gTWF0cml4Ojpjb2xTdW1zKGFycmF5W2dyZXBsKCJebXQtfC1tdC0iLCByb3duYW1lcyhhcnJheSkpLF0pIC8gYXJyYXkkbkNvdW50X1JOQQphcnJheSA8LSBBZGRNZXRhRGF0YShhcnJheSwgTWl0b19wcm9wb3J0aW9uLCBjb2wubmFtZSA9ICJNaXRvX3Byb3BvcnRpb24iKQoKUmlib19wcm9wb3J0aW9uIDwtIE1hdHJpeDo6Y29sU3VtcyhhcnJheVtncmVwbCgicnBsfHJwcyIsIHJvd25hbWVzKGFycmF5KSksXSkgLyBhcnJheSRuQ291bnRfUk5BCmFycmF5IDwtIEFkZE1ldGFEYXRhKGFycmF5LCBSaWJvX3Byb3BvcnRpb24sIGNvbC5uYW1lID0gIlJpYm9fcHJvcG9ydGlvbiIpCgojIGFkZCBncm91cCBpbmZvcm1hdGlvbiBieSBzdHJpbmcgc3BsaXR0aW5nIElELCByZW1vdmluZyByZXBsaWNhdGUKYXJyYXkgPC0gQWRkTWV0YURhdGEoYXJyYXksIHN0cnNwbGl0KGlkLCAiLSIpW1sxXV1bMV0sIGNvbC5uYW1lID0gInJlZ2lvbiIpCgpoZWFkKGFycmF5QG1ldGEuZGF0YSwgMykKCmNvdW50X3N0YXRzKCJjb3VudHMvY2VsbCIsIGFycmF5QG1ldGEuZGF0YSRuQ291bnRfUk5BKQpjb3VudF9zdGF0cygiZmVhdHVyZXMvY2VsbCIsIGFycmF5QG1ldGEuZGF0YSRuRmVhdHVyZV9STkEpCmNvdW50X3N0YXRzKCJtaXRvY2hvbmRyaWFsICUvY2VsbCIsIGFycmF5QG1ldGEuZGF0YSRNaXRvX3Byb3BvcnRpb24pCmNvdW50X3N0YXRzKCJyaWJvc29tYWwgJS9jZWxsIiwgYXJyYXlAbWV0YS5kYXRhJFJpYm9fcHJvcG9ydGlvbikKYGBgCgoqUGVyZm9ybSBjb21wbGV4aXR5IGZpbHRlcmluZyoKClJlbW92ZSBjZWxscyB3aXRoIGZld2VyIHRoYW4gMTAwMCBmZWF0dXJlcy4gU29tZSBkaWZmZXJlbnRpYWxseSBmaWx0ZXIgY2VsbHMgYW5kIGdsaWEsIGJ1dCBkb2luZyBib3RoIGF0IDEwMDAgc2hvdWxkIGJlIGZhciBsZXNzIGNvbXBsZXgsIGFuZCBzdWZmaWNpZW50IGZvciBvdXIgcHVycG9zZXMuIAoKYGBge3J9CgojIFN0b3JlIHByZS1maWx0ZXIgcG9wdWxhdGlvbiBtZXRyaWNzIGZvciBwbG90dGluZwpwbG90X2FycmF5IDwtIGNiaW5kLmRhdGEuZnJhbWUoYXJyYXkkbkNvdW50X1JOQSwgYXJyYXkkbkZlYXR1cmVfUk5BLCBhcnJheSRuRmVhdHVyZV9STkEgPj0gMTAwMCkKY29sbmFtZXMocGxvdF9hcnJheSkgPC0gYygiVU1JcyIsICJHZW5lcyIsICJGaWx0ZXJlZCIpCgojIFJlbW92ZSBhbGwgY2VsbHMgd2l0aCBmZXdlciB0aGFuIDEwMDAgVU1JcwphcnJheSA8LSBzdWJzZXQoYXJyYXksIGNlbGxzID0gY29sbmFtZXMoYXJyYXkpW2FycmF5JG5GZWF0dXJlX1JOQSA+PSAxMDAwXSkKCiMgT3V0cHV0IG5ldyBRQyBtZXRyaWNzCnBhc3RlKCJDZWxscyBwYXNzaW5nIDEwMDAgZ2VuZSBjb21wbGV4aXR5IGZpbHRlcjoiLCBsZW5ndGgoY29sbmFtZXMoYXJyYXkpKSkKcGFzdGUoIlVNSXMvY2VsbCBwYXNzaW5nIDEwMDAgZ2VuZSBjb21wbGV4aXR5IGZpbHRlcjoiLCBtZWFuKGFycmF5JG5Db3VudF9STkEpKQpwYXN0ZSgiRmVhdHVyZXMvY2VsbCBwYXNzaW5nIDEwMDAgZ2VuZSBjb21wbGV4aXR5IGZpbHRlcjoiLCBtZWFuKGFycmF5JG5GZWF0dXJlX1JOQSkpCnBhc3RlKCIlIG1pdG9jaG9uZHJpYWwvY2VsbCBwYXNzaW5nIDEwMDAgZ2VuZSBjb21wbGV4aXR5IGZpbHRlcjoiLCBtZWFuKGFycmF5JE1pdG9fcHJvcG9ydGlvbikpCnBhc3RlKCIlIHJpYm9zb21hbC9jZWxsIHBhc3NpbmcgMTAwMCBnZW5lIGNvbXBsZXhpdHkgZmlsdGVyOiIsIG1lYW4oYXJyYXkkUmlib19wcm9wb3J0aW9uKSkKCnBsb3RfbHkoZGF0YSA9IHBsb3RfYXJyYXksIHggPSB+VU1JcywgeSA9IH5HZW5lcywgY29sb3IgPSB+RmlsdGVyZWQpICU+JQogIGFkZF9tYXJrZXJzICU+JQogIGxheW91dCh0aXRsZSA9IHBhc3RlKCIxMDAwIEdlbmUgQ29tcGxleGl0eSBGaWx0ZXI6IiwgaWQpKQpgYGAKCipQZXJmb3JtIHF1YWxpdHkgZmlsdGVyaW5nOiBtaXRvY2hvbmRyaWFsIHJlYWRzKgoKQWNjb3JkaW5nIHRvIHBpcGVDb21wIChHZXJtYWluIGV0IGFsLiwgMjAyMCksIHJlbW92aW5nIG1pdG9jaG9uZHJpYWwgb3V0bGllcnMgbWlsZGx5IGluY3JlYXNlcyBwaXBlbGluZSBwZXJmb3JtYW5jZSBpZiBkb25lIGRvd25zdHJlYW0gd2l0aCBTQ1RyYW5zZm9ybS4gVGhpcyBjZWxsIGZpbHRlcnMgb3V0IGhpZ2ggb3V0bGllciBudWNsZWkgd2hlcmUgdGhlaXIgbWl0b2Nob25kcmlhbCByZWFkIHByb3BvcnRpb24gZXhjZWVkcyBRMys1eElRUi4KCmBgYHtyfQoKIyBTdG9yZSBwcmUtZmlsdGVyIHBvcHVsYXRpb24gbWV0cmljcyBmb3IgcGxvdHRpbmcKcGxvdF9hcnJheSA8LSBjYmluZC5kYXRhLmZyYW1lKGFycmF5JG5Db3VudF9STkEsIGFycmF5JE1pdG9fcHJvcG9ydGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcnJheSRNaXRvX3Byb3BvcnRpb24gPj0gcXVhbnRpbGUoYXJyYXkkTWl0b19wcm9wb3J0aW9uKVs0XSs1KklRUihhcnJheSRNaXRvX3Byb3BvcnRpb24pKQpjb2xuYW1lcyhwbG90X2FycmF5KSA8LSBjKCJVTUlzIiwgIk1pdG9jaG9uZHJpYWxfcHJvcG9ydGlvbiIsICJGaWx0ZXJlZCIpCgojIFJlbW92ZSBhbGwgZXh0cmVtZSBoaWdoIG91dGxpZXJzIGZvciBtaXRvY2hvbmRyaWFsIHJlYWRzLCBkZWZpbmVkIGFzIFEzKzUqSVFSLCB3aGljaCBpcyBhbiBFWFRSRU1FTFkgbGVuaWVudCBzdGFuZGFyZCAKYXJyYXkgPC0gc3Vic2V0KGFycmF5LCBjZWxscyA9IGNvbG5hbWVzKGFycmF5KVthcnJheSRNaXRvX3Byb3BvcnRpb24gPD0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1YW50aWxlKGFycmF5JE1pdG9fcHJvcG9ydGlvbilbNF0rNSpJUVIoYXJyYXkkTWl0b19wcm9wb3J0aW9uKV0pCgpwYXN0ZSgiQ2VsbHMgcGFzc2luZyBtaXRvY2hvbmRyaWFsIG91dGxpZXIgZmlsdGVyOiIsIGxlbmd0aChjb2xuYW1lcyhhcnJheSkpKQpwYXN0ZSgiVU1Jcy9jZWxsIHBhc3NpbmcgbWl0b2Nob25kcmlhbCBvdXRsaWVyIGZpbHRlcjoiLCBtZWFuKGFycmF5JG5Db3VudF9STkEpKQpwYXN0ZSgiRmVhdHVyZXMvY2VsbCBwYXNzaW5nIG1pdG9jaG9uZHJpYWwgb3V0bGllciBmaWx0ZXI6IiwgbWVhbihhcnJheSRuRmVhdHVyZV9STkEpKQpwYXN0ZSgiJSBtaXRvY2hvbmRyaWFsL2NlbGwgcGFzc2luZyBtaXRvY2hvbmRyaWFsIG91dGxpZXIgZmlsdGVyOiIsIG1lYW4oYXJyYXkkTWl0b19wcm9wb3J0aW9uKSkKcGFzdGUoIiUgcmlib3NvbWFsL2NlbGwgcGFzc2luZyBtaXRvY2hvbmRyaWFsIG91dGxpZXIgZmlsdGVyOiIsIG1lYW4oYXJyYXkkUmlib19wcm9wb3J0aW9uKSkKCnBsb3RfbHkoZGF0YSA9IHBsb3RfYXJyYXksIHggPSB+VU1JcywgeSA9IH5NaXRvY2hvbmRyaWFsX3Byb3BvcnRpb24sIGNvbG9yID0gfkZpbHRlcmVkKSAlPiUKICBhZGRfbWFya2VycyAlPiUKICBsYXlvdXQodGl0bGUgPSBwYXN0ZSgiTWl0b2Nob25kcmlhbCBPdXRsaWVyIEZpbHRlcjoiLCBpZCkpCmBgYAoKKlBlcmZvcm0gcXVhbGl0eSBmaWx0ZXJpbmc6IHJpYm9zb21hbCByZWFkcyoKCkFjY29yZGluZyB0byBwaXBlQ29tcCAoR2VybWFpbiBldCBhbC4sIDIwMjApLCByZW1vdmluZyByaWJvc29tYWwgb3V0bGllcnMgbWlsZGx5IGluY3JlYXNlcyBwaXBlbGluZSBwZXJmb3JtYW5jZSBpZiBkb25lIGRvd25zdHJlYW0gd2l0aCBTQ1RyYW5zZm9ybSBhbmQgaW4gdGFuZGVtIHdpdGggcmVtb3ZpbmcgbWl0b2Nob25kcmlhbCByZWFkIG91dGxpZXJzLiBUaGlzIGNlbGwgZmlsdGVycyBvdXQgaGlnaCBvdXRsaWVyIG51Y2xlaSB3aGVyZSB0aGVpciByaWJvc29tYWwgcmVhZCBwcm9wb3J0aW9uIGV4Y2VlZHMgUTMrNXhJUVIuCgpgYGB7cn0KCiMgU3RvcmUgcHJlLWZpbHRlciBwb3B1bGF0aW9uIG1ldHJpY3MgZm9yIHBsb3R0aW5nCnBsb3RfYXJyYXkgPC0gY2JpbmQuZGF0YS5mcmFtZShhcnJheSRuQ291bnRfUk5BLCBhcnJheSRSaWJvX3Byb3BvcnRpb24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXJyYXkkUmlib19wcm9wb3J0aW9uID49IHF1YW50aWxlKGFycmF5JE1pdG9fcHJvcG9ydGlvbilbNF0rNSpJUVIoYXJyYXkkUmlib19wcm9wb3J0aW9uKSkKY29sbmFtZXMocGxvdF9hcnJheSkgPC0gYygiVU1JcyIsICJSaWJvc29tYWxfcHJvcG9ydGlvbiIsICJGaWx0ZXJlZCIpCgojIFJlbW92ZSBhbGwgZXh0cmVtZSBoaWdoIG91dGxpZXJzIGZvciBtaXRvY2hvbmRyaWFsIHJlYWRzLCBkZWZpbmVkIGFzIFEzKzUqSVFSLCB3aGljaCBpcyBhbiBFWFRSRU1FTFkgbGVuaWVudCBzdGFuZGFyZCAKYXJyYXkgPC0gc3Vic2V0KGFycmF5LCBjZWxscyA9IGNvbG5hbWVzKGFycmF5KVthcnJheSRSaWJvX3Byb3BvcnRpb24gPD0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1YW50aWxlKGFycmF5JFJpYm9fcHJvcG9ydGlvbilbNF0rNSpJUVIoYXJyYXkkUmlib19wcm9wb3J0aW9uKV0pCgpwYXN0ZSgiQ2VsbHMgcGFzc2luZyByaWJvc29tYWwgb3V0bGllciBmaWx0ZXI6IiwgbGVuZ3RoKGNvbG5hbWVzKGFycmF5KSkpCnBhc3RlKCJVTUlzL2NlbGwgcGFzc2luZyByaWJvc29tYWwgb3V0bGllciBmaWx0ZXI6IiwgbWVhbihhcnJheSRuQ291bnRfUk5BKSkKcGFzdGUoIkZlYXR1cmVzL2NlbGwgcGFzc2luZyByaWJvc29tYWwgb3V0bGllciBmaWx0ZXI6IiwgbWVhbihhcnJheSRuRmVhdHVyZV9STkEpKQpwYXN0ZSgiJSBtaXRvY2hvbmRyaWFsL2NlbGwgcGFzc2luZyByaWJvc29tYWwgb3V0bGllciBmaWx0ZXI6IiwgbWVhbihhcnJheSRNaXRvX3Byb3BvcnRpb24pKQpwYXN0ZSgiJSByaWJvc29tYWwvY2VsbCBwYXNzaW5nIHJpYm9zb21hbCBvdXRsaWVyIGZpbHRlcjoiLCBtZWFuKGFycmF5JFJpYm9fcHJvcG9ydGlvbikpCgpwbG90X2x5KGRhdGEgPSBwbG90X2FycmF5LCB4ID0gflVNSXMsIHkgPSB+Umlib3NvbWFsX3Byb3BvcnRpb24sIGNvbG9yID0gfkZpbHRlcmVkKSAlPiUKICBhZGRfbWFya2VycyAlPiUKICBsYXlvdXQodGl0bGUgPSBwYXN0ZSgiUmlib3NvbWFsIE91dGxpZXIgRmlsdGVyOiIsIGlkKSkKYGBgCgoqVEVNUE9SQVJZIFVOVElMIFNPTE8gV09SS1M6IHJlbW92ZSBkb3VibGV0cyoKCmBgYHtyfQpyZXF1aXJlKFNldXJhdCkKCgpzY2UgPC0gYXMuU2luZ2xlQ2VsbEV4cGVyaW1lbnQoYXJyYXkpICU+JQogICAgc2NEYmxGaW5kZXIKCnBsb3RfYXJyYXkgPC0gY2JpbmQuZGF0YS5mcmFtZShhcnJheSRuQ291bnRfUk5BLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFycmF5JG5GZWF0dXJlX1JOQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjZSRzY0RibEZpbmRlci5jbGFzcykKY29sbmFtZXMocGxvdF9hcnJheSkgPC0gYygiVU1JcyIsICJGZWF0dXJlcyIsICJEb3VibGV0X3N0YXR1cyIpCgphcnJheSA8LSBzdWJzZXQoYXJyYXksIGNlbGxzID0gY29sbmFtZXMoYXJyYXkpW3NjZSRzY0RibEZpbmRlci5jbGFzcyA9PSAic2luZ2xldCJdKQoKcGFzdGUoIkNlbGxzIHBhc3NpbmcgZG91YmxldCBmaWx0ZXI6IiwgbGVuZ3RoKGNvbG5hbWVzKGFycmF5KSkpCnBhc3RlKCJVTUlzL2NlbGwgcGFzc2luZyBkb3VibGV0IGZpbHRlcjoiLCBtZWFuKGFycmF5JG5Db3VudF9STkEpKQpwYXN0ZSgiRmVhdHVyZXMvY2VsbCBwYXNzaW5nIGRvdWJsZXQgZmlsdGVyOiIsIG1lYW4oYXJyYXkkbkZlYXR1cmVfUk5BKSkKcGFzdGUoIiUgbWl0b2Nob25kcmlhbC9jZWxsIHBhc3NpbmcgZG91YmxldCBmaWx0ZXI6IiwgbWVhbihhcnJheSRNaXRvX3Byb3BvcnRpb24pKQpwYXN0ZSgiJSByaWJvc29tYWwvY2VsbCBwYXNzaW5nIGRvdWJsZXQgZmlsdGVyOiIsIG1lYW4oYXJyYXkkUmlib19wcm9wb3J0aW9uKSkKCnBsb3RfbHkoZGF0YSA9IHBsb3RfYXJyYXksIHggPSB+VU1JcywgeSA9IH5GZWF0dXJlcywgY29sb3IgPSB+RG91YmxldF9zdGF0dXMpICU+JQogIGFkZF9tYXJrZXJzICU+JQogIGxheW91dCh0aXRsZSA9IHBhc3RlKCJEb3VibGV0IEZpbHRlcjoiLCBpZCkpCmBgYAoKU2F2ZSBwcmUtcHJvY2Vzc2VkIG1hdHJpeCBmb3IgdXNlIGRvd25zdHJlYW0KCmBgYHtyfQpleHBvcnRfcGF0aCA8LSBwYXJhbXMkb3V0cHV0X3BhdGgKc2F2ZVJEUyhhcnJheSwgZmlsZSA9IHBhc3RlMChleHBvcnRfcGF0aCwgaWQsICJfcHJlcHJvY2Vzc2VkLnJkcyIpKQpgYGA=